Skip to content

Feature/inequality operator#4454

Closed
Ritvik718 wants to merge 2 commits into
less:masterfrom
Ritvik718:feature/inequality-operator
Closed

Feature/inequality operator#4454
Ritvik718 wants to merge 2 commits into
less:masterfrom
Ritvik718:feature/inequality-operator

Conversation

@Ritvik718

@Ritvik718 Ritvik718 commented Jun 15, 2026

Copy link
Copy Markdown

What:

  • Added parsing recognition for the inequality operator (!=) in packages/less/lib/less/parser/parser.js.
  • Implemented evaluation support for the inequality operator in packages/less/lib/less/tree/condition.js:
    • Returns true when comparison results in inequality (-1 or 1).
    • Returns false when comparison results in equality (0).
    • Returns true when comparison is undefined (types are not comparable, meaning they are not equal).
  • Added comprehensive unit tests in packages/test-data/tests-unit/css-guards/css-guards.less and css-guards.css.

Why:
Currently, Less lacks an inequality comparison operator, requiring users to write verbose guard checks like not (@foo = 1) rather than the cleaner and standard @foo != 1. This aligns Less with standard CSS/JS and other CSS preprocessors.

Checklist:

  • Documentation - N/A
  • Added/updated unit tests
  • Code complete

Summary by CodeRabbit

  • New Features

    • Added support for the != (not-equal) operator in guard conditions.
  • Improvements

    • Enhanced benchmark reporting with better compiler detection and error handling.
  • Tests

    • Added comprehensive test coverage for inequality guard operators.

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jun 15, 2026
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds != as a recognized comparison operator in Less guard conditions by updating the parser's atomicCondition and the Condition.eval() switch logic, with new test fixtures covering all four inequality cases. Separately refactors benchmark-runner.js CLI parsing, compiler discovery, and benchmark execution flow, and changes a template literal to a quoted string in rollup.js.

Changes

!= guard operator support

Layer / File(s) Summary
Parser and eval logic for !=
packages/less/lib/less/parser/parser.js, packages/less/lib/less/tree/condition.js
atomicCondition detects the != token and sets op = '!='; Condition.eval() includes '!=' in the case -1/case 1 operator sets and replaces the hardcoded false default with this.op === '!='.
Guard inequality test fixtures
packages/test-data/tests-unit/css-guards/css-guards.less, packages/test-data/tests-unit/css-guards/css-guards.css
Adds four != guard selectors in the .less source; adds the expected .inequality-test-1 and .inequality-test-3 output rules in the .css fixture.

Benchmark runner refactor and rollup cosmetic fix

Layer / File(s) Summary
Benchmark runner CLI, compiler discovery, and execution
packages/less/benchmark/benchmark-runner.js, packages/less/build/rollup.js
Refactors --key=value parsing loop, adds a clearer missing-file guard, adds default-export interop and render/parse validation during compiler lookup, introduces hrNow(), completed/errors tracking, runOnce/runAll sequential execution with bail-out after 3 errors, and JSON reporting. rollup.js changes a template literal to a single-quoted string in moduleShim().load().

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • less/less.js#4410: Also modifies benchmark-runner.js's --key=value CLI option parsing and forwarding of extra options into less.render.

Poem

🐇 Hop, hop — not equal today!
The parser now knows != is here to stay,
Guards check the difference with logic so neat,
Green for the unequal, red for repeat.
The benchmark runs tidy, the rollup string plain —
A patch full of hops down the fast lane! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/inequality operator' clearly and specifically summarizes the main change: adding support for the inequality operator (!=) to the Less language, which is the primary objective across the parser, evaluation logic, and test additions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/test-data/tests-unit/css-guards/css-guards.less (1)

104-115: ⚡ Quick win

Add one fixture that covers non-comparable inequality.

The new cases validate comparable != paths, but they don’t exercise the non-comparable branch that should still evaluate != as true. Add one guard like when (foo != 1) and assert it emits CSS.

Suggested fixture addition
 .inequality-test-4 when (`@b` != 2) {
   color: red;
 }
+
+.inequality-test-5 when (foo != 1) {
+  color: green;
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/test-data/tests-unit/css-guards/css-guards.less` around lines 104 -
115, Add a new test fixture after the existing inequality-test cases that
validates non-comparable inequality evaluation. Create a guard like when(foo !=
1) where foo is an undefined or non-comparable variable, and include a CSS rule
(such as color: green;) that should be emitted when the guard evaluates to true.
This test should demonstrate that the non-comparable branch in the inequality
operator evaluation correctly returns true when comparing incompatible types or
undefined values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/less/benchmark/benchmark-runner.js`:
- Around line 46-47: The compiler discovery condition at line 46 currently uses
OR logic to accept compilers that have either `render` or `parse`, but the
`runOnce()` function always calls `less.render` at line 93. Change the condition
from `(less.render || less.parse)` to `(less.render && less.parse)` to require
both methods to be present, preventing runtime crashes when a parse-only
compiler is selected.

---

Nitpick comments:
In `@packages/test-data/tests-unit/css-guards/css-guards.less`:
- Around line 104-115: Add a new test fixture after the existing inequality-test
cases that validates non-comparable inequality evaluation. Create a guard like
when(foo != 1) where foo is an undefined or non-comparable variable, and include
a CSS rule (such as color: green;) that should be emitted when the guard
evaluates to true. This test should demonstrate that the non-comparable branch
in the inequality operator evaluation correctly returns true when comparing
incompatible types or undefined values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 01e6e179-62bd-4278-9565-48b010ac24d4

📥 Commits

Reviewing files that changed from the base of the PR and between 7787557 and a1edc9c.

📒 Files selected for processing (6)
  • packages/less/benchmark/benchmark-runner.js
  • packages/less/build/rollup.js
  • packages/less/lib/less/parser/parser.js
  • packages/less/lib/less/tree/condition.js
  • packages/test-data/tests-unit/css-guards/css-guards.css
  • packages/test-data/tests-unit/css-guards/css-guards.less

Comment on lines +46 to +47
if (less && (less.render || less.parse)) {
lessPath = p;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require render during compiler discovery to prevent runtime crashes.

At Line 46, discovery currently accepts compilers that only expose parse. But runOnce() always calls less.render (Line 93), so a parse-only module will fail at runtime with TypeError: less.render is not a function.

Proposed fix
-        if (less && (less.render || less.parse)) {
+        if (less && typeof less.render === 'function') {
             lessPath = p;
             break;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (less && (less.render || less.parse)) {
lessPath = p;
if (less && typeof less.render === 'function') {
lessPath = p;
break;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/less/benchmark/benchmark-runner.js` around lines 46 - 47, The
compiler discovery condition at line 46 currently uses OR logic to accept
compilers that have either `render` or `parse`, but the `runOnce()` function
always calls `less.render` at line 93. Change the condition from `(less.render
|| less.parse)` to `(less.render && less.parse)` to require both methods to be
present, preventing runtime crashes when a parse-only compiler is selected.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for the != inequality operator in Less guard conditions by extending both parsing and condition evaluation, and updates unit fixtures to validate expected CSS output. The PR also includes ancillary changes to build/benchmark tooling that are not directly related to the new operator.

Changes:

  • Parse != in guard conditions (parser.js) and evaluate it in Condition.eval (condition.js).
  • Add guard fixtures verifying != behavior in css-guards.less/css.
  • Adjust build shim output string and refactor/format the benchmark runner.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/test-data/tests-unit/css-guards/css-guards.less Adds new guard cases using != to validate behavior.
packages/test-data/tests-unit/css-guards/css-guards.css Updates expected CSS output for the new != guard cases.
packages/less/lib/less/tree/condition.js Implements evaluation semantics for != based on Node.compare results.
packages/less/lib/less/parser/parser.js Extends condition parsing to recognize != as a comparison operator.
packages/less/build/rollup.js Tweaks module shim string emission (scope/tooling change).
packages/less/benchmark/benchmark-runner.js Refactors/cleans up benchmark runner logic/formatting (scope/tooling change).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +51 to +53
} catch (e) {
// try next
}
}

// Standard deviation and coefficient of variation
var sumSqDiff = 0;
for (i = 0; i < effective.length; i++) {
Comment on lines +104 to +115
.inequality-test-1 when (2 != 1) {
color: green;
}
.inequality-test-2 when (2 != 2) {
color: red;
}
.inequality-test-3 when (@b != 1) {
color: green;
}
.inequality-test-4 when (@b != 2) {
color: red;
}
Comment on lines 28 to 32
},
load(id) {
if (id === '\0module') {
return `export function createRequire() { return require; }`;
return 'export function createRequire() { return require; }';
}
@matthew-dean

Copy link
Copy Markdown
Member

Guard syntax / conditions is modelled on media query syntax. It doesn't have a != so we'd be unlikely to as well. not(2 = 2) is still quite readable

@Ritvik718

Copy link
Copy Markdown
Author

Thanks for the explanation, Matthew! That makes complete sense. I hadn't fully considered the design alignment with CSS media query syntax when putting this together.

Since not(a = b) is indeed the idiomatic way to handle this in Less and aligns with CSS standards, I completely understand closing this. Thanks again for your time and feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants